spec(047): §14 Phase 3 completion — register Phase 3 descriptors + close engine gaps#440
Merged
Merged
Conversation
…pView descriptor Adds the engine-gap closer carried forward from Phase 3 finish: the typed `TemplatedFlipViewElement<T>` peer now routes through V1 dispatch via a new `PreMountedItems<TElement, TControl>` ChildrenStrategy and a base-derived `TemplatedFlipViewDescriptor` registered on `TemplatedFlipViewElementBase`. Engine - IItemsBinderStrategy.Bind signature widened to take `Element? oldElement` (null on Mount, set on Update). Keeps the consolidated dispatch arm in V1HandlerAdapter + DescriptorHandler to a single is-check + interface call. All 4 existing implementers ignore the new param (they read prior state from the control) — only the new PreMountedItems<> uses it. - PreMountedItems<TElement, TControl> in ChildrenStrategy.cs: pre-mounts every item up-front through IItemViewSource into the control's IList<object> Items sink, and on Update positionally reconciles via Reconciler.ReconcileV1Child for shared slots, appending new tail slots and truncating excess. Rubber-duck recommendations adopted: Debug.Assert on (oldElement is TElement) and on `items.Count == oldSource.ItemCount` with release fallback to full rebuild if the invariant breaks; throws InvalidOperationException (not silent null writes) if mount/reconcile returns null in a slot. Descriptor - TemplatedFlipViewDescriptor uses PreMountedItems<> + HandCodedControlled for SelectedIndex with the existing FlipViewEventPayload shared trampoline slot. `callback` returns a synthetic non-null delegate gated on `el.HasCallbacks` so the engine only subscribes when at least one closed-T leaf has wired OnSelectedIndexChanged. - Registered base-derived in RegisterV1BuiltInHandlers via RegisterHandlerForDerivedTypes<TemplatedFlipViewElementBase, FlipView>. Tests - New Desc_TemplatedFlipView_MountUpdate covers: pre-mount item-count, pre-mounted slots are UIElements, initial SelectedIndex applied, mount didn't fire callback, programmatic SelectedIndex write echo-suppressed, grow/shrink/edit-in-place positional reconcile, shrink clamps SelectedIndex, same-ref idempotency, edit-in-place preserves slot identity (CanUpdate path through ReconcileV1Child). - New Desc_TemplatedFlipView_NoCallback_DoesNotSubscribe covers the HasCallbacks=false trampoline-not-subscribed branch. Docs - §14 carry-forwards: TemplatedFlipView engine-gap closed; updated stale `TemplatedFlipViewDescriptor stays carved` comment in FlipViewDescriptor. Validation - dotnet test tests/Reactor.Tests -c Release -p:Platform=x64 → 9134 ok / 0 fail - --self-test --filter `Desc_` V1 ON = 630 ok / 0 fail; V1 OFF = 630 ok / 0 fail (parity holds, both flags +17 vs Phase 3 finish baseline of 613 for the new TemplatedFlipView fixtures). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a SemanticElement descriptor backed by SemanticPanel with SingleContent child reconciliation and descriptor selftest coverage for mount/update parity. Validated with: dotnet build src\Reactor\Reactor.csproj -c Release -p:Platform=x64; dotnet build tests\Reactor.AppTests.Host\Reactor.AppTests.Host.csproj -c Release -p:Platform=x64; dotnet run --project tests\Reactor.AppTests.Host -c Release -p:Platform=x64 --no-build -- --self-test --filter Desc_Semantic Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Port the untyped GridViewElement path to a V1 descriptor using ItemsHost and descriptor-managed selection/item-click events. Add the descriptor selftest and registry entries for mount/update coverage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Port ItemContainerElement to a V1 single-content descriptor. Add descriptor selftest coverage for mount, child update, and IsSelected update. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add a descriptor for the AnnounceRegionElement hidden TextBlock live-region anchor and register descriptor selftest coverage for mount/update parity. Validated with: dotnet build src\Reactor\Reactor.csproj -c Release -p:Platform=x64; dotnet build tests\Reactor.AppTests.Host\Reactor.AppTests.Host.csproj -c Release -p:Platform=x64; dotnet run --project tests\Reactor.AppTests.Host -c Release -p:Platform=x64 --no-build -- --self-test --filter Desc_AnnounceRegion Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Port ItemsViewElementBase to a base-derived V1 descriptor using TemplatedItemsErased. Extend the erased keyed binder to reproduce the legacy ItemsView ItemsSource/ItemTemplate shape and add descriptor selftest coverage for mount/update and property changes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Defer IconElement: the current V1 descriptor engine requires a stable TControl identity, while IconElement's legacy path chooses different native IconElement subclasses from IconData and can replace the native control when the IconData subtype changes. This needs an element-aware polymorphic factory or an approved replacement strategy before it can route through _v1Handlers without semantic drift. Defer XamlHostElement: its factory returns arbitrary user-owned FrameworkElement instances, so the descriptor model cannot express the control type, pooling policy, or ownership safely through a single new() TControl descriptor. Defer XamlPageElement: it is closer to descriptor-shaped than XamlHost, but the bridge still delegates to Frame.Navigate for arbitrary user XAML page types. The attempted descriptor selftest could not provide a deterministic code-only page target without crashing the WinUI navigation path, so this stays carved for orchestrator review rather than landing an unvalidated descriptor. Validated with: dotnet build tests\Reactor.AppTests.Host\Reactor.AppTests.Host.csproj -c Release -p:Platform=x64; dotnet run --project tests\Reactor.AppTests.Host -c Release -p:Platform=x64 --no-build -- --self-test --filter Desc_Semantic; dotnet run --project tests\Reactor.AppTests.Host -c Release -p:Platform=x64 --no-build -- --self-test --filter Desc_AnnounceRegion Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Selftest: Desc_AnimatedVisualPlayer_MountUpdate (V1 ON). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Selftest: Desc_AnnotatedScrollBar_MountUpdate (V1 ON). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Selftest: Desc_MapControl_MountUpdate (V1 ON descriptor availability only). MapControl construction process-terminates this headless host without the Maps runtime/token, so E2E must own real lifecycle coverage. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Selftest: Desc_ParallaxView_MountUpdate (V1 ON). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Selftest: Desc_RefreshContainer_MountUpdate (V1 ON). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Selftest: Desc_SwipeControl_MountUpdate (V1 ON). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Selftest: Desc_SemanticZoom_MountUpdate (V1 ON). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Selftest: Desc_MediaPlayerElement_MountUpdate (V1 ON, no media source). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Selftest: Desc_WebView2_MountUpdate (V1 ON, no Source to avoid async CoreWebView2 init). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Selftest: Desc_TitleBar_MountUpdate (V1 ON). SetTitleBar is deferred to Loaded so the title bar is attached before window registration. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Selftest: Desc_NavigationView_MountUpdate (V1 ON). Selection trampoline handles built-in settings selection by reporting a null SelectedTag because WinUI does not expose a NavigationViewItem tag for Settings. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Batch 3 SemanticZoom fixture referenced a non-existent ListViewDescriptor for its inner ZoomedInView/ZoomedOutView ListViewElement children. Use the Phase 1 hand-coded ListViewHandler instead (the canonical V1 registration for ListView). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ension
Adds a new decorator-style V1 handler shape for elements whose returned
UIElement identity may change on update or whose unmount disposition
diverges from the standard pool-return. Targets the 8 elements deferred
during Phase 3 batches:
- Target-wrapping decorators: FlyoutElement, MenuFlyoutElement,
CommandBarFlyoutElement (returned UIElement IS the user's inner
Target child — type isn't known until inspection).
- Modal lifecycle wrappers: ContentDialogElement, PopupElement
(returned control is a placeholder; the actual modal surface is
side-mounted or lazy).
- Polymorphic mounts: IconElement (concrete control type depends
on the element's runtime subtype).
- Interop bridges: XamlHostElement, XamlPageElement.
Surface changes (3 minimum-correct items per rubber-duck spec):
1. IV1HandlerEntry.Update returns UIElement (was void). Standard
handlers (V1HandlerAdapter) always return `control` unchanged,
preserving §13 Q12's no-substitution invariant on the standard
IElementHandler surface. Reconciler.Update threads the result
into the parent slot via the existing legacy-registry path
(already handled by ReconcileV1Child).
2. IV1HandlerEntry.Unmount returns V1UnmountDisposition enum
(CollectSelf | ContinueDefaultTraversal | SkipPool). Standard
handlers always return CollectSelf, matching pre-extension
behavior. Reconciler.UnmountRecursive + UnmountAndCollect switch
on the returned disposition to control pool collection and
traversal recursion.
3. New IDecoratorElementHandler<TElement> contract (TElement only,
no TControl) + V1DecoratorHandlerAdapter<TElement> bridging into
IV1HandlerEntry. Distinct from IElementHandler<TElement,TControl>
so the public author-facing surface retains its no-substitution
invariant; only built-in V1 ports use this decorator shape.
Registered via Reconciler.RegisterDecoratorHandler<TElement>
(internal — built-in ports only).
No descriptor registrations changed in this commit. Descriptor ports
for the 8 deferred elements follow in subsequent commits.
Validation:
- dotnet build src/Reactor/Reactor.csproj: 0 errors
- dotnet build tests/Reactor.AppTests.Host: 0 errors
- --self-test --filter Desc_ V1 ON: 728 ok / 0 failures
- --self-test --filter Desc_ V1 OFF: 728 ok / 0 failures
- Desc_ parity: ON ≡ OFF (no regression introduced by widening).
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…duction V1 dispatch
Wires every Phase 3 descriptor authored across batches 1–4 (plus the
Phase 1 hand-coded handlers + base-derived templated/lazy/items hosts +
PreMountedItems TemplatedFlipView) into RegisterV1BuiltInHandlers, the
sole built-in V1 registration site. With this PR, V1 ON becomes the
production dispatch path for ~78 element types; only the carve-list
elements documented in the RegisterV1BuiltInHandlers XML doc still fall
through to the legacy Mount/Update switch.
Carve list (intentionally NOT registered — documented inline):
- Composition primitives (Component, Func, Memo, ErrorBoundary,
CommandHost, Validation.*) — sit ABOVE the V1 handler protocol
- Interop bridges (XamlHost, XamlPage) — descriptors exist but
XamlInterop.Register populates _typeRegistry at startup, would
clash with EnsureRegistrableElementType; unification = Phase 4
- Deferred overlays (ContentDialog, Flyout, MenuBar, MenuFlyout,
CommandBar, CommandBarFlyout, Popup) — require decorator-style
ports for modal lifecycle; follow-up PR
- Deferred stateful host (NavigationHost) — per-instance route/cache
state intercepted in UnmountRecursive before V1 dispatch arm;
needs small refactor; follow-up PR
- TabViewDescriptor — bisect (3× clean V1 ON full selftest with
only this carved, vs. 1–4 random docking-text-find failures per
run when registered) ratifies the descriptor's documented gaps:
spec 045 §2.4 drag pipeline (OnTabDragStarting/Completed), §2.2
pinnable headers (BuildTabHeader/BuildPinButton/in-place
TryUpdatePinHeaderInPlace), in-place CanUpdate for tab content
(preserves focus/state on re-renders), conditional SelectedIndex
write, TabStripHeader/Footer Element slots. Closing requires
engine work (post-children mount-hook so SelectionChanged
subscribes after children-add + ImperativeBridged for named tab
strip slots); tracked alongside overlays + NavigationHost.
Validation:
- dotnet test tests/Reactor.Tests -c Release -p:Platform=x64:
Passed! 9134 / 0 failed / 62 skipped
- dotnet run --project tests/Reactor.AppTests.Host --self-test
V1 ON (run 1): 4410 ok, 0 failures
V1 ON (run 2): 4410 ok, 0 failures
V1 OFF: 4410 ok, 0 failures
- Desc_ filter A|B: 744 / 744 (perfect parity)
Helpers added (internal, [Experimental(`REACTOR_V1_PREVIEW'')]):
RegisterDescriptor<TElement, TControl>(descriptor)
RegisterDescriptorForDerivedTypes<TBase, TControl>(descriptor)
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ompletion
Document Phase 3 completion in both the spec (§14) and the implementation
tracker. With the engine extension (IDecoratorElementHandler) +
PreMountedItems strategy + descriptor batches + production registration
sweep landed:
- Engine gap closed (TemplatedFlipView via PreMountedItems)
- Every Phase 3 batch descriptor authored AND registered:
- Untyped items hosts: GridView, ItemsView, ItemContainer
- Heavy/specialized: WebView2, NavigationView, TitleBar,
MediaPlayerElement, AnimatedVisualPlayer, MapControl,
SemanticZoom, AnnotatedScrollBar, RefreshContainer,
SwipeControl, ParallaxView
- Polymorphic/a11y: IconElement (via new IDecoratorElementHandler),
SemanticElement, AnnounceRegion
Intentional carve list (documented inline in RegisterV1BuiltInHandlers
and re-enumerated in both docs for the follow-up reviewer):
- Dialog/overlay family (ContentDialog, Flyout, Popup, MenuBar,
MenuFlyout, CommandBar, CommandBarFlyout) — modal lifecycle needs
decorator-style ports beyond the IDecoratorElementHandler shape
- NavigationHost — UnmountRecursive intercepts before V1 arm
- TabViewDescriptor — bisect-ratified gaps need engine work
(post-children mount-hook for safe SelectionChanged wiring +
ImperativeBridged for tab strip slots)
- XamlHost/XamlPage — XamlInterop.Register clashes with V1 auto-reg
- Composition primitives (Component, Func, Memo, ErrorBoundary,
CommandHost, Validation.*) — sit ABOVE V1 protocol; Phase 4 keeps
their legacy arms
A|B parity bar met: 9134 xunit + 4410 selftest (V1 ON ≡ V1 OFF),
0 failures both flags across 3 consecutive full V1 ON runs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…80% arms; 76/87 = 87% of V1-reachable) Updates §14 in both the spec and tracker to reference PR #440 and add an explicit coverage table breaking the 95 legacy switch arms into 76 routed / 11 reachable-deferred / 8 permanent composition-primitive carves, plus the enumerated path-to-100% for the follow-up PR. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| case FontIconData fi when icon is WinUI.FontIcon fontIcon: | ||
| fontIcon.Glyph = fi.Glyph; | ||
| if (fi.FontFamily is not null) | ||
| fontIcon.FontFamily = new FontFamily(fi.FontFamily); |
| await Harness.Render(); | ||
|
|
||
| H.Check("Desc_Semantic_UpdatedValue", panel.SemanticValue == "4 of 5"); | ||
| H.Check("Desc_Semantic_UpdatedRange", panel.RangeValue == 4); |
| await Harness.Render(); | ||
|
|
||
| H.Check("Desc_AnnotatedScrollBar_Mounted", true); | ||
| H.Check("Desc_AnnotatedScrollBar_InitialWidth", asb.Width == 48); |
| var el2 = AnnotatedScrollBar().Set(c => c.Width = 72); | ||
| rec.UpdateChild(el1, el2, asb, _noOp); | ||
| await Harness.Render(); | ||
| H.Check("Desc_AnnotatedScrollBar_UpdatedWidth", asb.Width == 72); |
| await Harness.Render(); | ||
|
|
||
| H.Check("Desc_ParallaxView_Mounted", true); | ||
| H.Check("Desc_ParallaxView_InitialVerticalShift", pv.VerticalShift == 12); |
| var el2 = ParallaxView(TextBlock("updated"), verticalShift: 24, horizontalShift: 6); | ||
| rec.UpdateChild(el1, el2, pv, _noOp); | ||
| await Harness.Render(); | ||
| H.Check("Desc_ParallaxView_UpdatedVerticalShift", pv.VerticalShift == 24); |
| await Harness.Render(); | ||
|
|
||
| H.Check("Desc_WebView2_Mounted", true); | ||
| H.Check("Desc_WebView2_InitialWidth", wv.Width == 320); |
| var el2 = WebView2().Set(c => c.Width = 480); | ||
| rec.UpdateChild(el1, el2, wv, _noOp); | ||
| await Harness.Render(); | ||
| H.Check("Desc_WebView2_UpdatedWidth", wv.Width == 480); |
| H.Check("Desc_Semantic_Mounted", true); | ||
| H.Check("Desc_Semantic_Role", panel.SemanticRole == "slider"); | ||
| H.Check("Desc_Semantic_Value", panel.SemanticValue == "3 of 5"); | ||
| H.Check("Desc_Semantic_Range", panel.RangeMinimum == 0 && panel.RangeMaximum == 5 && panel.RangeValue == 3); |
| H.Check("Desc_Semantic_Mounted", true); | ||
| H.Check("Desc_Semantic_Role", panel.SemanticRole == "slider"); | ||
| H.Check("Desc_Semantic_Value", panel.SemanticValue == "3 of 5"); | ||
| H.Check("Desc_Semantic_Range", panel.RangeMinimum == 0 && panel.RangeMaximum == 5 && panel.RangeValue == 3); |
There was a problem hiding this comment.
Pull request overview
This PR completes Spec 047 §14 Phase 3 wiring by moving many built-in element types onto the V1 handler/descriptor dispatch path, while documenting the remaining carved legacy paths.
Changes:
- Registers Phase 1 handlers, Phase 3 descriptors, base-derived descriptor handlers, and the Icon decorator handler in
RegisterV1BuiltInHandlers. - Adds decorator-handler/unmount-disposition support and
PreMountedItems<>for templatedFlipView. - Adds descriptor fixtures and updates spec/task documentation with Phase 3 completion status and remaining carve list.
Show a summary per file
| File | Description |
|---|---|
src/Reactor/Core/Reconciler.cs |
Expands built-in V1 registrations and unmount disposition handling. |
src/Reactor/Core/Reconciler.Update.cs |
Allows V1 update handlers to return replacement controls. |
src/Reactor/Core/Reconciler.KeyedItemsBinding.cs |
Adds keyed binding support for ItemsView. |
src/Reactor/Core/Element.cs |
Makes ItemsViewElementBase expose keyed item source behavior. |
src/Reactor/Core/V1HandlerRegistry.cs |
Updates V1 handler entry contract for update return/unmount disposition. |
src/Reactor/Core/V1Protocol/V1HandlerAdapter.cs |
Returns controls from standard updates and forwards old elements to binders. |
src/Reactor/Core/V1Protocol/V1DecoratorHandlerAdapter.cs |
Adds adapter for decorator-style V1 handlers. |
src/Reactor/Core/V1Protocol/IDecoratorElementHandler.cs |
Defines decorator handler API and unmount disposition enum. |
src/Reactor/Core/V1Protocol/ChildrenStrategy.cs |
Extends item binder contract and adds PreMountedItems<>. |
src/Reactor/Core/V1Protocol/ControlEventPayloads.cs |
Adds payload slots for new descriptor event trampolines. |
src/Reactor/Core/V1Protocol/Descriptor/DescriptorHandler.cs |
Passes old elements into item binder strategies. |
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/AnimatedVisualPlayerDescriptor.cs |
Adds descriptor for AnimatedVisualPlayer. |
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/AnnotatedScrollBarDescriptor.cs |
Adds descriptor for AnnotatedScrollBar. |
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/AnnounceRegionDescriptor.cs |
Adds descriptor for live-region announcement anchor. |
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/FlipViewDescriptor.cs |
Updates documentation for templated FlipView handling. |
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/GridViewDescriptor.cs |
Adds descriptor for plain GridViewElement. |
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/IconDescriptor.cs |
Adds decorator-style descriptor for polymorphic icons. |
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/ItemContainerDescriptor.cs |
Adds descriptor for ItemContainerElement. |
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/ItemsViewDescriptor.cs |
Adds descriptor for typed ItemsView elements. |
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/MapControlDescriptor.cs |
Adds descriptor for MapControl. |
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/MediaPlayerElementDescriptor.cs |
Adds descriptor for MediaPlayerElement. |
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/NavigationViewDescriptor.cs |
Adds descriptor for NavigationView. |
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/ParallaxViewDescriptor.cs |
Adds descriptor for ParallaxView. |
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/RefreshContainerDescriptor.cs |
Adds descriptor for RefreshContainer. |
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/SemanticDescriptor.cs |
Adds descriptor for accessibility semantic wrapper. |
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/SemanticZoomDescriptor.cs |
Adds descriptor for SemanticZoom. |
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/SwipeControlDescriptor.cs |
Adds descriptor for SwipeControl. |
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/TemplatedFlipViewDescriptor.cs |
Adds descriptor using PreMountedItems<>. |
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/TitleBarDescriptor.cs |
Adds descriptor for TitleBar. |
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/WebView2Descriptor.cs |
Adds descriptor for WebView2. |
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/XamlHostDescriptor.cs |
Adds unregistered decorator descriptor for XamlHost. |
src/Reactor/Core/V1Protocol/Descriptor/Descriptors/XamlPageDescriptor.cs |
Adds unregistered decorator descriptor for XamlPage. |
tests/Reactor.AppTests.Host/SelfTest/Fixtures/Spec047V1ProtocolDescriptorFixtures.cs |
Adds descriptor selftest coverage for new ports. |
tests/Reactor.AppTests.Host/SelfTest/SelfTestFixtureRegistry.cs |
Registers new selftest fixtures. |
docs/specs/047-extensible-control-model.md |
Updates Phase 3 completion status and coverage counts. |
docs/specs/tasks/047-extensible-control-model-implementation.md |
Updates task status, deferred scope, and cleanup guidance. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 36/36 changed files
- Comments generated: 4
Comment on lines
+48
to
+52
| Children = new ItemsHost<GridViewElement, WinUI.GridView>( | ||
| GetItems: static e => (IReadOnlyList<object>)e.Items, | ||
| GetCollection: static c => c.Items), | ||
| GetSetters = static e => e.Setters, | ||
| } |
| shouldWrite: static e => !double.IsNaN(e.ExpandedModeThresholdWidth)) | ||
| .Imperative( | ||
| mount: static (c, e) => ApplyMenuAndSelection(c, oldElement: null, e), | ||
| update: static (c, o, n) => ApplyMenuAndSelection(c, o, n)) |
Comment on lines
+570
to
+577
| // Collection / source-count drift would corrupt positional pairing. | ||
| // Debug-assert; release falls through to the same rebuild path as | ||
| // the type-mismatch case via the count clamp below. | ||
| global::System.Diagnostics.Debug.Assert( | ||
| items.Count == oldCount, | ||
| "PreMountedItems<>: control items collection (" + items.Count | ||
| + ") drifted from previous source ItemCount (" + oldCount + ")."); | ||
| if (items.Count != oldCount) oldCount = items.Count; |
| protocol — Phase 4 cleanup keeps their legacy arms):** | ||
|
|
||
| - `ComponentElement`, `FuncElement`, `MemoElement`, | ||
| `ErrorBoundaryElement`, `CommandHostElement`, `ModifiedElement`, |
* Carve GridViewDescriptor — its ItemsHost<> strategy pre-mounts every item into GridView.Items (no virtualization), diverging from legacy MountGridView's ItemsSource+CCC lazy realization. A|B tests still pass, but production memory/lifecycle would silently regress; closing needs hand-coded GridViewHandler or RecyclingItemsHost<> shape. Updated coverage table: 75 routed / 12 deferred / 8 primitives. * Align legacy UpdateNavigationView with V1 NavigationViewDescriptor — add PaneDisplayMode, IsSettingsVisible, conditional PaneTitle, MenuItems rebuild on ref-change, and SelectedItem re-selection (with new FindNavItemByTag helper). Previously legacy only wrote a subset of these on update; reviewer correctly flagged the A|B divergence for record-with updates that change MenuItems / Selected. * Fix PreMountedItems<> release fallback at ChildrenStrategy.cs:577 — the count-drift path was clamping oldCount=items.Count and continuing positional reconciliation, which would index past oldSource bounds when items.Count > oldSource.ItemCount or skip stale source items' teardown when smaller. Replaced with the same full-rebuild path as the type-mismatch release fallback above. * Doc fix: remove ModifiedElement from the tracker's composition- primitive carve list — it's unwrapped before V1/legacy dispatch at the top of Reconciler.Mount, not a switch arm; coverage table stays at 8 primitives. Validation: dotnet test 9134/0; full V1 ON selftest 4410/0; full V1 OFF selftest 4410/0 (each suite required one re-run to clear the known FloatRoot/Reliability/DockHooks docking flake family). Ignored 9 github-code-quality bot comments: 8 are integer-valued double == checks in test fixtures (values are assigned constants, exact equality is reliable); 1 claims WinUI FontFamily is IDisposable (it is not). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Spec 047 §14 Phase 3 completion. Wires every Phase 3 descriptor authored
across batches 1–4 (plus the Phase 1 hand-coded handlers + base-derived
templated/lazy/items hosts +
PreMountedItemsTemplatedFlipView) intoRegisterV1BuiltInHandlers, the sole built-in V1 registration site.With this PR, V1 ON becomes the production dispatch path for ~76 element
types; only the explicit carve list documented inline still falls through
to the legacy Mount/Update switch.
This is a last A|B PR but not the last A|B PR — one follow-up PR
remains before Phase 4 cleanup (overlays + NavigationHost + TabView gap
closure). The brief permits this split. The bar this PR clears: V1 ON ≡
V1 OFF across the full xunit + selftest matrix for every registered
element.
Validation
dotnet test tests/Reactor.Tests--self-test(full, run 1)--self-test(full, run 2)--self-test --filter ""Desc_""Two consecutive full V1 ON runs at 4410/0 confirmed stable A|B parity
after the TabViewDescriptor carve (see
TabViewDescriptorsectionbelow for the bisect that motivated the carve).
What this PR adds
Production wiring
RegisterV1BuiltInHandlers(Reconciler.cs) — full sweep of ~76descriptors organized as: 5 Phase 1 hand-coded → 6 base-derived
(TemplatedListView/GridView/FlipView, LazyStack, ItemsRepeater,
ItemsView) → 64 standard concrete (alphabetical) → 1 decorator
(IconElement via the new IDecoratorElementHandler).
RegisterDescriptor<TElement, TControl>and
RegisterDescriptorForDerivedTypes<TBase, TControl>(
[Experimental(""REACTOR_V1_PREVIEW"")]).Engine extension (already committed earlier in branch)
IDecoratorElementHandler(commit 9109395) for IconElement-styledecorators whose Target type varies at runtime.
PreMountedItems<TElement, TControl>ChildrenStrategy(commit 9f0244a) closing the TemplatedFlipView gap (FlipView has
no
ContainerContentChanging).Descriptor additions (already committed earlier in branch)
MediaPlayerElement, AnimatedVisualPlayer, MapControl, SemanticZoom,
AnnotatedScrollBar, RefreshContainer, SwipeControl, ParallaxView.
AnnounceRegion.
Carve list (legacy switch arm → registration mapping)
The brief required this exhaustive cross-check. The legacy
Reconciler.Mountswitch inReconciler.Mount.cs(~lines 79–177)has 95 element type arms. Each falls into exactly one of:
Registered through V1 (76 arms) — every entry below now routes
through
_v1Handlerswhen V1 ON:V1Protocol/Handlers/*Handler.csRegisterDescriptorForDerivedTypes)Standard descriptor-driven (alphabetical, 64 arms):
AnimatedIcon, AnimatedVisualPlayer, AnnotatedScrollBar, AnnounceRegion,
AutoSuggestBox, BreadcrumbBar, Button, CalendarDatePicker, CalendarView,
Canvas, CheckBox, ColorPicker, ComboBox, DatePicker, DropDownButton,
Ellipse, Expander, FlexPanel, FlipView, Frame, Grid, GridView,
HyperlinkButton, Icon (decorator), Image, InfoBadge, InfoBar,
ItemContainer, Line, ListBox, MapControl, MediaPlayerElement,
NavigationView, NumberBox, ParallaxView, PasswordBox, Path,
PersonPicture, PipsPager, Pivot, Progress, ProgressRing, RadioButton,
RadioButtons, RatingControl, Rectangle, RefreshContainer, RelativePanel,
RepeatButton, RichEditBox, RichTextBlock, ScrollView, ScrollViewer,
SelectorBar, Semantic, SemanticZoom, SplitButton, SplitView, Stack,
SwipeControl, TeachingTip, TextBlock, TimePicker, TitleBar,
ToggleButton, ToggleSplitButton, TreeView, Viewbox, WebView2, WrapGrid.
Carved (19 arms) — kept on the legacy switch by design, documented
inline in
RegisterV1BuiltInHandlers:_typeRegistryat startup, V1 auto-reg would clash viaEnsureRegistrableElementType. Unification = Phase 4UnmountRecursiveBEFORE V1 dispatch arm; needs refactor19 + 76 = 95. Every switch arm accounted for. ✓
TabViewDescriptorbisect rationaleThe descriptor exists but stays carved from registration. With the
descriptor registered, full V1 ON full selftest produces 1–4
non-deterministic docking-text-find failures per run across
DockHooks / PixDoc / RoleAware / Composition / FloatRoot fixtures
(three runs observed 4, 2, 1 failures respectively). With the
descriptor's single line of registration commented out, three
consecutive full V1 ON runs each hit 4410 ok / 0 failures, perfectly
matching the V1 OFF baseline. Bisect confirms TabViewDescriptor is
the source.
The failures ratify the gaps already documented on
TabViewDescriptor.cs:(
OnTabDragStarting/OnTabDragCompleted)(
BuildTabHeader/BuildPinButton/ in-placeTryUpdatePinHeaderInPlaceto preserve focus on re-render)TabItemsHostdoes a naive full-replace on Update vs. legacyUpdateTabView's in-placeCanUpdatefor tab content(clobbers focus/state)
SelectedIndexwrite (writing on everyupdate clobbers user's current tab in uncontrolled scenarios)
TabStripHeader/TabStripFooterElement slots stayescape-hatched
Closing them requires engine work (post-children mount-hook so
SelectionChangedsubscribes AFTER children-add,ImperativeBridgedfor the named tab strip slots), tracked alongside the overlays +
NavigationHost in the follow-up PR.
Phase 4 cleanup scope (the PR after the follow-up)
For the next agent / reviewer — Phase 4 cleanup deletes:
MountXxx/UpdateXxxarm inReconciler.Mount.cs/Reconciler.Update.csfor an elementtype that this PR (and the follow-up) routes through V1.
Reconciler.Update.cs.MountXxx/UpdateXxxprivate methods orphaned by (1).UseV1Protocolflag,Reactor.UseV1ProtocolAppContext switch,and the legacy fallback branch in
Reconciler.Mount.ChangeEchoSuppressorper §8 audit — if §8 audit succeeded,delete; otherwise document as Phase 4.5.
EventHandlerStateper §9 (§9.2 struct shapes).What stays in the legacy switch (Phase 4 keeps these arms):
Follow-up PR scope (between this and Phase 4 cleanup)
children mount-hook for safe SelectionChanged wiring, ImperativeBridged
for tab strip slots.
MenuBar, MenuFlyout, CommandBar, CommandBarFlyout, Popup. Modal
lifecycle needs a new decorator strategy variant or
IDecoratorElementHandlerextension.MountNavigationHost/UpdateNavigationHost, duplicate cleanuplogic in V1 handler so
UnmountRecursive's intercept can beremoved.
XamlInterop.Registerto allow descriptor-driven path.Remaining acceptance gates (deferred — call out for next PR)
follow-up. Target capture date: 2026-05-28. To be landed under
docs/specs/047/phase3-results/before the next PR opens.under
docs/specs/047/phase3-results/CPC-ander-YTZ3O-x64-advisory/<date>-phase3-completion-3x5/with a README explaining the delta vs.
2026-05-28-phase3-finish-3x5/.Files changed
src/Reactor/Core/Reconciler.cs— RegisterV1BuiltInHandlerssweep + two helpers + carve-list XML doc.
docs/specs/047-extensible-control-model.md— §14 statusupdate reflecting Phase 3 completion + remaining carve list.
docs/specs/tasks/047-extensible-control-model-implementation.md— same.
Earlier in branch (already in main..HEAD before this commit):